package com.dycong.common.database.mongoDB;

import com.ifunpay.util.function.ThrewFunction;
import com.ifunpay.util.jackson.JsonUtil;
import com.ifunpay.util.mongo.MongoConfig;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.log4j.Log4j2;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author Administrator
 */
@Log4j2
public class MongoUtil {
    public static Document mapToDocument(Map<String, ?> map) {
        Document document = new Document();
        document.putAll(map);
        return document;
    }


    public static Document pojoToDocument(Object o) {
        return mapToDocument(Arrays.stream(o.getClass().getDeclaredFields())
                .map((ThrewFunction<Field, Document>) f -> {
                    f.setAccessible(true);
                    Object value = f.get(o);
                    // convert Date's subclass to Date.
                    if (value instanceof Date) {
                        value = new Date(((Date) value).getTime());
                    }
                    return new Document(f.getName(), value);
                }).reduce((a, b) -> {
                    a.putAll(b);
                    return a;
                }).orElse(null)
        );
    }


    public static void insert(MongoCollection<Document> c, Document... documents) {
        try {
            List<Document> list = Arrays.asList(documents);
            c.insertMany(list);
            if (log.isTraceEnabled()) {
                log.trace("mongo save ok -> " + list);
            }
        } catch (RuntimeException e) {
            log.trace("mongo insert error", e);
            throw e;
        }
    }

    public static void insert(String collectionName, Document... documents) {
        insert(MongoConfig.getCollection(collectionName), documents);
    }


    public static void insertIfPossible(String collectionName, Document... documents) {
        MongoConfig.execute(() -> {
            try {
                insert(collectionName, documents);
            } catch (Throwable throwable) {
                log.debug("TRY TO INSERT FAILED, ");
                log.trace("", throwable);
            }
        });
    }


    public static List<Document> saveObjects(MongoCollection<Document> c, Object... objects) {
        try {
            List<Document> documentList = Arrays.stream(objects)
                    .map(JsonUtil::toJson)
                    .map(Document::parse)
                    .collect(Collectors.toList());
            c.insertMany(documentList);
            log.trace("mongo insert ok ");
            return documentList;
        } catch (RuntimeException e) {
            log.trace("mongo insert error", e);
            throw e;
        }
    }

    public static List<Document> saveObjects(String collectionName, Object... objects) {
        return saveObjects(MongoConfig.getCollection(collectionName), objects);
    }

    public static List<Document> saveObjectsIfPossible(String collectionName, Object... objects) {
        try {
            return MongoConfig.submit(() -> saveObjects(collectionName, objects)).get();
        } catch (Throwable throwable) {
            log.debug("TRY TO SAVE TO MONGODB FAILED", throwable);
        }
        return null;
    }


    public static UpdateResult updateOne(String collectionName, Document filter, Document updateTo) {
        return MongoConfig.getCollection(collectionName).updateOne(filter, updateTo);
    }

    public static UpdateResult updateOne(String collectionName, Document filter, Document updateTo, UpdateOptions options) {
        return MongoConfig.getCollection(collectionName).updateOne(filter, updateTo, options);
    }


    public static List<Document> find(String collectionName, int start, int size) {
        return find(collectionName, null, start, size);
    }

    public static List<Document> find(String collectionName, Document filter, int start, int size) {
        if (filter == null) {
            filter = new Document();
        }
        List<Document> list = new ArrayList<>();
        MongoConfig.getCollection(collectionName).find(filter).skip(start).limit(size).iterator().forEachRemaining(list::add);
        return list;
    }

    public static Optional<Document> findOne(String collectionName, Document filter) {
        return find(collectionName, filter, 0, 1).stream().findFirst();
    }

    public static Optional<Document> get(String collectionName, Object id) {
        return findOne(collectionName, new Document("_id", id));
    }


    public static DeleteResult deleteMany(String collectionName, Bson filter) {
        return MongoConfig.getCollection(collectionName).deleteMany(filter);
    }

    public static DeleteResult deleteOne(String collectionName, Bson filter) {
        return MongoConfig.getCollection(collectionName).deleteOne(filter);
    }

    public static long count(String collectionName) {
        return MongoConfig.getCollection(collectionName).count();
    }

    public static long count(String collectionName, Bson filter) {
        if (filter == null) {
            filter = new Document();
        }
        return MongoConfig.getCollection(collectionName).count(filter);
    }
}
